package ucd.csi.comp30220.fall2011.oteop.traffic.impl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Collection;
import java.util.Iterator;

import javax.jmdns.JmDNS;
import javax.jmdns.ServiceInfo;
import javax.jms.JMSException;
import javax.naming.NamingException;

import ucd.csi.comp30220.fall2011.oteop.OTEOP;
import ucd.csi.comp30220.fall2011.oteop.flughafen.AirportException;
import ucd.csi.comp30220.fall2011.oteop.flughafen.AirportFinder;
import ucd.csi.comp30220.fall2011.oteop.flughafen.AirportInfo;
import ucd.csi.comp30220.fall2011.oteop.collision.Collision;
import ucd.csi.comp30220.fall2011.oteop.collision.CollisionListener;
import ucd.csi.comp30220.fall2011.oteop.collision.CollisionSubscriber;
import ucd.csi.comp30220.fall2011.oteop.flight.Flight;
import ucd.csi.comp30220.fall2011.oteop.flight.FlightException;
import ucd.csi.comp30220.fall2011.oteop.flight.FlightFinder;
import ucd.csi.comp30220.fall2011.oteop.flight.FlightInfo;
import ucd.csi.comp30220.fall2011.oteop.flight.FlightStatus;
import ucd.csi.comp30220.fall2011.oteop.navigation.GreatCircle;
import ucd.csi.comp30220.fall2011.oteop.traffic.Traffic;
import ucd.csi.comp30220.fall2011.oteop.util.PortFinder;

public class TrafficServer extends ControlTower {
	
	private String name;
	private JmDNS jmdns;
	private ServiceInfo info;
	private List<String> flights;
	private boolean listen;
	private Thread thread;
	private ServerSocket server;
	
	public TrafficServer(String name) throws Exception {
		this.name = name;
		listen = true;
		
		int port = PortFinder.getNextAvailable();
		server = new ServerSocket(port);
		
		jmdns = JmDNS.create();
		info = ServiceInfo.create(
	            OTEOP.JMDNS_TYPE_TCP_LOCAL, 
	            TrafficClient.getTraffic(name), 
	            Traffic.JMDNS_SUB_TYPE, 
	            port, 
	    		name);
		jmdns.registerService(info);
		System.out.println("AirTraffic registered on " +port);
		
		// Manage Collisions.
		CollisionSubscriber sub;
			sub = new CollisionSubscriber();
			sub.addListener(new CollisionListener() {
				public void onCollisionUpdate(Collision collision) {
					Object[] col = collision.flights().toArray();
					Flight f1 = FlightFinder.find(((FlightStatus)col[0]).getNumber());
					Flight f2 = FlightFinder.find(((FlightStatus)col[1]).getNumber());
					//alterCourse(f1, f2); 
					try {
					    f1.collisionDetected(collision); 
					    //f2.collisionDetected(collision); 
					} catch (Exception e) {
					    e.printStackTrace();
					}
				}
			});
		
		// Manage socket connections from clients.
		thread = new Thread(new Runnable(){
			public void run() {
				System.out.println("Thread started.");
				while(!Thread.currentThread().isInterrupted()){
					try {
						manageClient(server.accept());

					} catch (IOException e) {
					    e.printStackTrace();
					}
				}
				
			}});
		thread.start();
		
		// Manage ATC-Airport hand-overs.
		new Thread(new Runnable(){
			public void run() {
				while(true){
					Collection<Flight> allFlights = schedule.values();
					Iterator<Flight> it = allFlights.iterator();
					List<String> removes = new ArrayList<String>();
					
					while(it.hasNext()){
						Flight flight = it.next();
						try {
						    FlightInfo info = flight.getInfo();
						    if(GreatCircle.distanceBetween(flight.getPosition(), info.getDestination()) < 50) {
						        handOverFlight(info);
						        removes.add(info.getNumber());
						    }
						} catch(FlightException e) {
							e.printStackTrace();
						}
					}
					for (String remove : removes) {
					    removeFlight(remove);
					}
					try { Thread.sleep(10000); }
					catch (InterruptedException ie) {}
				}
			}
		}).start();
	}
	
	/*
	 * Stop the server and unregister its service.
	 */
	public void stop() { 
		unregisterService();
		thread.interrupt();
		try {
			server.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/*
	 * Hand over a flight from ATC to Airport.
	 */
	public void handOverFlight(FlightInfo fInfo){
		// Hand over flight to appropriate airport, only remove if successful.
		// Communication method involved - Airport stub.
		try {
			AirportFinder.find(fInfo.getDestinationAirportInfo().getCode()).acceptFlight(fInfo);
		} catch (AirportException e) {
			e.printStackTrace();
		}
	}
	
	/*
	 * Spawn new thread upon connection, this thread receives a Flight Number which is added to the Schedule.
	 */
	private void manageClient(final Socket client) {
		new Thread(new Runnable(){
			public void run() {
				try {
					BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
					String flightNum = in.readLine();
					Flight flight = FlightFinder.find(flightNum);
					System.err.println("### Got: " +flight);
					addFlight(flightNum, flight);
					chartCourse(flight, 10);
					in.close();
					client.close();
				}
				catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
	}	
	
	/*
	 * Unregister service from ZeroConf.
	 */
	
	public void unregisterService(){
		jmdns.unregisterService(info);
		try {
			jmdns.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
